<script setup>

import UseChatStore from "@/stores/useChatStore.js";
import { chatCompletionV1 } from "@/api/audio.js";
import { blobToBase64, detectSpeech } from "@/utils/ystool.js";
import { VueMarkdownIt } from "@f3ve/vue-markdown-it";

const { proxy } = getCurrentInstance();

// 聊天缓存
const chatStore = UseChatStore();


// 录音音频数据
const recordedAudioData = ref([]);
// 录音机
const recorder = ref(null);
// 移动距离阈值 - 阈值可以根据需要调整
const cancelThreshold = ref(20);
// 记录当前位置
const startY = ref(0)
// 限制是否取消问答
let shouldCancel = ref(false);
// 对话按钮
const btnText = ref('按住说话');
// 是否正在播放音频
const isPlay = ref(false);
// 加载层
const loading = ref(false);
// 机器人音频数据播放
const audio = ref(null);
// 是否开启导航
const open = ref(false)
// 计算机器人播放时间
const durationTime = ref(0)

// 请求浏览器音频访问
const requestAudioAccess = () => {
  navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {
    // 媒体录像机
    recorder.value = new MediaRecorder(stream);
    bindEvent();
  }).catch(error => {
    console.log(error);
    proxy.$modal.notifyError("出错，请确保已允许浏览器获取录音权限")
  });
};

// 事件录入数据和发送请求
const bindEvent = () => {
  // 录音数据
  recorder.value.ondataavailable = getRecordingData;
  // 结束播放就开始发起请求
  recorder.value.onstop = saveRecordingData;
}


// 实时获取音频数据
const getRecordingData = (event) => {
  recordedAudioData.value.push(event.data);
};

/**
 * 说话完毕 发起聚合接口
 */
const saveRecordingData = () => {
  // false才能发起
  if (shouldCancel.value) {
    return
  }
  let blob = new Blob(recordedAudioData.value, { type: 'audio/ogg; codecs=opus' });
  let audioStream = URL.createObjectURL(blob);

  // 检测音频数据是否包含语音活动
  const reader = new FileReader();
  reader.readAsArrayBuffer(blob);
  reader.onloadend = () => {
    const audioContext = new (window.AudioContext || window.webkitAudioContext)();
    audioContext.decodeAudioData(reader.result, (audioBuffer) => {
      const rawData = audioBuffer.getChannelData(0); // 获取音频数据
      const isSpeaking = detectSpeech(rawData);
      console.log("Is Speaking: ", isSpeaking);

      if (isSpeaking) {
        // 清空之前的音频数据
        recordedAudioData.value = []
        // 录音结束
        sendRemote(audioStream, blob)
      } else {
        proxy.$modal.notifyError("请确保您正在说话!")
        console.log("结束");
        recordedAudioData.value = []
        onStop()
      }
    });
  };
}

/**
 * 调用聚合接口函数
 */
const sendRemote = (audioStream, blob) => {
  if (shouldCancel.value) {
    return;
  }
  let duration = parseInt(blob.size / 6600);
  if (duration <= 2) {
    proxy.$modal.notifyError("说话时间太短,至少5秒!")
    recordedAudioData.value = [];
    return;
  }

  if (duration > 60) {
    duration = 60;
  }

  // 这里调用语腾讯云语音识别功能拿到文本
  // 然后根据文本调用元器智能体获取回复文本
  // 然后根据文本进行语音合成出定制的语音风格
  // 返回前端进行播放
  // 发请求
  blobToBase64(blob).then(base64Audio => {
    loading.value = true
    chatCompletionV1({
      "audioFile": base64Audio
    }).then(res => {
      console.log(res);
      // 聚合接口返回来的数据格式
      const msgText = res.data.userMessage
      const botMessage = res.data.botMessage
      const voice = res.data.botVoice

      if (msgText.length <= 0) {
        proxy.$modal.notifyError("您没说话，请重新说话!")
        loading.value = false
        recordedAudioData.value = [];
        return
      }


      // TODO 用户
      chatStore.pushMessage({
        duration: duration,
        stream: audioStream,
        isUser: true,
        text: msgText,
        sort: chatStore.addIndex(), // 当前消息排序 +1
      })

      // 计算音频时长
      calculateDuration(voice, botMessage)


      // 滚动
      scrollToBottom()

      setTimeout(() => {
        loading.value = false
        // 设置未播放
        isPlay.value = false
        // 进行流试(伪)
        chatStore.updateMessage(chatStore.messageList, botMessage, true)
        // 播放最新的一个语音
        onPlay(chatStore.messageList.length - 1)
        // 清空录音数据
        recordedAudioData.value = [];
      }, 50)
    }).catch(err => {
      console.log(err);
      proxy.$modal.notifyError("出错，请稍后再试!")
      loading.value = false
      recordedAudioData.value = [];
    })
  })

}


// 滚动到底部
const scrollToBottom = () => {
  setTimeout(() => {
    // 确保 scrollContainerRef.value 是定义的
    if (proxy.$refs.scrollContainerRef) {
      proxy.$refs.scrollContainerRef.scrollTop = proxy.$refs.scrollContainerRef.scrollHeight;
    }
  }, 10)
};


// 根据索引播放机器人音频
const onPlay = (index) => {
  // 暂停全部音频的动画
  // chunkList.value.forEach(item => {
  //   item.wink = false;
  // });

  let item = chatStore.messageList[index];
  item.wink = false;
  audio.value.src = item.stream;

  // 正在播放
  if (isPlay.value) {
    // 则表示暂停
    audio.value.pause();
    isPlay.value = false
  } else {
    audio.value.play();
    isPlay.value = true
  }

  chatStore.messageList[index] = item

  bindAudioEvent(index);
};

/**
 * 绑定音频事件,变更动画
 */
const bindAudioEvent = (index) => {
  let item = chatStore.messageList[index];

  audio.value.onplaying = () => {
    item.wink = true;
  };

  audio.value.onended = () => {
    item.wink = false;
  };

  // 动画变更
  chatStore.messageList[index] = item
};


/**
 * 计算音频时长
 * @param voice 机器人音频
 * @param botMessage 机器人消息
 */
const calculateDuration = (voice, botMessage) => {
  if ([null,undefined].includes(voice)) {
    // TODO 机器人
    chatStore.pushMessage({
      duration: 999,
      stream: null,
      isUser: false,
      text: botMessage,
      sort: chatStore.addIndex(), // 当前消息排序 +1
    });
    return
  }
  const binaryString = window.atob(voice.split(',')[1]);
  const len = binaryString.length;
  const bytes = new Uint8Array(len);
  for (let i = 0; i < len; i++) {
    bytes[i] = binaryString.charCodeAt(i);
  }
  const blob = new Blob([ bytes ], { type: 'audio/mp3' });
  const audio = new Audio();
  audio.src = URL.createObjectURL(blob);
  audio.addEventListener('loadedmetadata', () => {
    // TODO 机器人
    chatStore.pushMessage({
      duration: Math.round(audio.duration),
      stream: voice,
      isUser: false,
      text: botMessage,
      sort: chatStore.addIndex(), // 当前消息排序 +1
    });
  });
}

// 按下
const onMousedown = (event) => {
  startY.value = event.clientY; // 或使用event.touches[0].clientY对于触摸事件
  btnText.value = '松开结束, 下滑取消问答';
  onStart();
};

// 松开
const onMouseup = (event) => {
  // 停止录音
  onStop();

  if (shouldCancel.value) {
    proxy.$modal.msgSuccess("取消问答")
    setTimeout(() => {
      // 清空音频数据
      recordedAudioData.value = []
      shouldCancel.value = false;
      btnText.value = '按住说话';
      // 重置按钮按下位置
      startY.value = 0
    }, 20)

    return; // 如果应该取消，则不执行任何操作
  }

  setTimeout(() => {
    //成功也要取消
    shouldCancel.value = false;
    btnText.value = '按住说话';
    // 重置按钮按下位置
    startY.value = 0
    // 发送请求
    // sendRemote()
  }, 200)
};

// 在鼠标移动时
const onMouseMove = (event) => {
  if (startY.value <= 10) {
    return
  }
  const currentY = event.clientY;
  const distance = Math.sqrt(Math.pow(currentY - startY.value, 2));
  // console.log(distance, cancelThreshold.value);
  // 超过距离
  if (distance > cancelThreshold.value) {
    btnText.value = '松开取消';
    // 设置 true 限制发送请求
    shouldCancel.value = true;
  } else {
    shouldCancel.value = false;
    btnText.value = '松开结束, 下滑取消问答';
  }
};

// 悬浮上
const onTouchmove = (event) => {
  event.preventDefault();
  onMouseMove(event.touches[0]);
};

// 开启
const onStart = () => {
  if (recorder.value) {
    recorder.value.start();
  }
};

// 停止
const onStop = () => {
  if (recorder.value) {
    recorder.value.stop();
  }
};

// 监听文本变化
watch(chatStore.messageList, (newValue, oldValue) => {
  scrollToBottom()
});

// 生命周期函数 - mounted
onMounted(() => {
  requestAudioAccess()
  scrollToBottom()
})
</script>

<template>
  <div class="audio-wrapper">
    <div class="agent">
      <div class="agent-body" v-loading="loading">
        <div class="agent-head">
          <div class="img" id="agent-head-logo"></div>
          <span>童话匠</span>
        </div>
        <div class="agent-content" id="agent-content">
          <div class="msg-list" name="fade" ref="scrollContainerRef">
            <div class="msg">
              <div class="avatar2"></div>
              <div class="audio2">
                欢迎来到玩转新声 - 童话匠 By腾讯云社区领袖杨不易呀
              </div>
            </div>

            <!--      渲染消息      -->
            <ul v-for="(item, index) in chatStore.messageList" :key="index">
              <!-- 用户 -->
              <li v-if="item.isUser" class="msg">
                <!--       头像区域         -->
                <div :class="{ avatar:true }"></div>
                <!--       内容区域         -->
                <div v-cloak :class="{ audio:true }">
                  {{ item.text }}
                </div>
              </li>

              <!-- 机器人 -->
              <li v-else class="msg">
                <!--       头像         -->
                <div :class="{ avatar2: true }"></div>
                <!--       内容区域         -->
                <div v-cloak :class="{  audio2: true,  'duration-item':true, wink: item.wink }">
                  <div class="markdown">
                    <!--  markdown格式  -->
                    <vue-markdown-it v-if="item.duration > 0" class="markdown-it" :source="item.text "/>
                  </div>

                  <!--  语音区域 -->
                  <div class="voice-content" @click="onPlay(index)" v-if="item.duration > 0"
                       @touchend.prevent="onPlay(index)">
                    <div class="bg voicePlay"></div>
                    <div class="duration2">{{ item.duration }}"</div>
                  </div>


                </div>
              </li>
            </ul>
          </div>
        </div>
        <div class="height-bg"
             @mousemove="onMouseMove"
             @touchmove.prevent="onTouchmove"
             @mousedown="onMousedown"
             @touchstart.prevent="onMousedown"
             @mouseup="onMouseup"
             @touchend.prevent="onMouseup"
        >
          <div id="agent-operate" :class="{'agent-operate': true, 'agent-operate-red': shouldCancel}">
            {{ btnText }}
          </div>
        </div>

      </div>
    </div>
    <audio ref="audio"></audio>
  </div>


  <el-tour v-model="open">
    <el-tour-step target="#phone-head-logo" title="童话匠Logo"
                  description="这里是Logo,您点击下去将会弹出抽屉供您选择不同智能体的对话氛围!"
    />
    <el-tour-step
      target="#phone-content"
      title="问答区域"
      description="用户与机器人对话"
    />
    <el-tour-step
      target="#phone-operate"
      title="按住说话"
      description="进行语音识别发送问答信息"
    />
  </el-tour>
</template>

<style scoped lang="scss">
.audio-wrapper {
  margin-top: 10px;
  padding: 20px;
  width: 100%;
}


.agent {
  margin: 168px auto;
  padding: 55px 11px 53px;
  width: 221px;
  height: 448px;
  font-size: 12px;
  border-radius: 35px;
  background-image: url("../assets/img/iphone-bg.png");
  box-sizing: border-box;
  user-select: none;
  transform: scale(1.7);
}

.agent-body {
  height: 100%;
  background-color: #fff;
}

.agent-head {
  height: 30px;
  line-height: 30px;
  color: #000;
  background-color: transparent;
  text-align: center;
  position: relative;
}

.agent-head .img {
  width: 20px;
  height: 20px;
  background: url("https://pinia.vuejs.org/logo.svg") 1px 28px;
  background-size: 100%;
  border-radius: 50%;
  position: absolute;
  left: 5px;
  top: 5px;
  animation: shake_logo_img 1s infinite;


  @keyframes shake_logo_img {
    0% {
      transform: rotate(0deg);
    }
    25% {
      transform: rotate(5deg);
    }
    50% {
      transform: rotate(0deg);
    }
    75% {
      transform: rotate(-5deg);
    }
    100% {
      transform: rotate(0deg);
    }
  }

}

.agent-head span {
  display: inline-block;
}

.agent-head span:nth-child(2) {
  //width: 100px;
  text-align: center;
}

.agent-head span:nth-child(3) {
  float: right;
  margin-right: 10px;
}

.agent-content {
  height: 282px;
  background-color: #f1eded;
}

.agent-container {
  line-height: 28px;
  width: 100%;
}

.height-bg {
  height: 100px;
  background: transparent;
}

.agent-operate {
  color: #222222;
  position: relative;
  line-height: 28px;
  text-align: center;
  cursor: pointer;
  font-weight: bold;
  box-shadow: 0 -1px 1px rgba(0, 0, 0, .1);
}

.agent-operate:active {
  background-color: rgba(255, 255, 255, 0.65);
}

.agent-operate-red {
  background-color: rgba(215, 53, 53, 0.91) !important;
}

.agent-operate:active:before {
  position: absolute;
  left: 50%;
  transform: translate(-50%, 0);
  top: -2px;
  content: '';
  width: 0%;
  height: 4px;
  background-color: #7bed9f;
  animation: loading 1s ease-in-out infinite backwards;
}

.msg-list {
  margin: 0;
  padding: 0;
  height: 100%;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

.msg-list::-webkit-scrollbar {
  display: none;
}

.msg-list .msg {
  list-style: none;
  padding: 0 8px;
  margin: 10px 0;
  overflow: hidden;
  //cursor: pointer;
}

.msg-list .msg .avatar,
.msg-list .msg .audio,
.msg-list .msg .duration {
  float: right;
}

.msg-list .msg .avatar2,
.msg-list .msg .audio2 {
  float: left;
}


.msg-list .msg .avatar, .msg-list .msg .avatar2 {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  line-height: 24px;
  text-align: center;
  background-color: #000;
  background: url("../assets/img/yby.png") 0 0;
  background-size: 100%;
}

.msg-list .msg .avatar2 {
  background: url('../assets/img/yq.png') -25px 74px !important;
  background-size: 100% !important;
  transform: scale(1.0);
}

.msg-list .msg .audio, .msg-list .msg .audio2 {
  position: relative;
  margin-right: 6px;
  max-width: 125px;
  min-width: 30px;
  height: 24px;
  line-height: 24px;
  padding: 0 4px 0 10px;
  border-radius: 2px;
  color: #000;
  text-align: left;
  background-color: rgba(107, 197, 107, 0.85);
}

.msg-list .msg .audio2 {
  margin-left: 6px;
  text-align: left;

}


.msg-list .msg.eg {
  cursor: default;
}

.msg-list .msg.eg .audio {
  text-align: left;
}

.msg-list .msg .audio:before {
  position: absolute;
  right: -8px;
  top: 8px;
  content: '';
  display: inline-block;
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 4px;
  border-color: transparent transparent transparent rgba(107, 197, 107, 0.85);
}

.msg-list .msg .audio2:before {
  position: absolute;
  left: -8px;
  top: 8px;
  content: '';
  display: inline-block;
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 4px;
  border-color: transparent rgba(107, 197, 107, 0.85) transparent transparent;
}

.msg-list .msg .audio span, .msg-list .msg .audio2 span {
  color: rgba(255, 255, 255, .8);
  display: inline-block;
  transform-origin: center;
}

.msg-list .msg .audio span:nth-child(1) {
  font-weight: 400;
}

.msg-list .msg .audio span:nth-child(2) {
  transform: scale(0.8);
  font-weight: 500;
}

.msg-list .msg .audio span:nth-child(3) {
  transform: scale(0.5);
  font-weight: 700
}

.msg-list .msg .audio2 span:nth-child(1) {
  transform: scale(0.5);
  font-weight: 300;
}

.msg-list .msg .audio2 span:nth-child(2) {
  transform: scale(0.8);
  font-weight: 400;
}

.msg-list .msg .audio2 span:nth-child(3) {
  font-weight: 500;

}

.msg-list .msg .audio.wink .voicePlay,
.msg-list .msg .audio2.wink .voicePlay {
  animation-name: voicePlay;
  animation-duration: 1s;
  animation-direction: normal;
  animation-iteration-count: infinite;
  animation-timing-function: steps(3);
  top: 0 !important;
}

.duration-item {
  position: relative;
}

//.msg-list .msg .duration, .msg-list .msg .duration2 {
//  //margin: 3px 2px;
//  color: rgba(255, 255, 255, 0.73);
//  position: absolute;
//  right: 18px;
//  top: 0;
//}

.msg-list .msg .duration2 {
  color: rgba(255, 255, 255, 0.73);
  margin-left: 1px;
  font-size: 10px;
}

.fade-enter-active, .fade-leave-active {
  transition: opacity .5s;
}

.fade-enter, .fade-leave-to {
  opacity: 0;
}

//@keyframes wink {
//  from {
//    color: rgba(255, 255, 255, .8);
//  }
//  to {
//    color: rgba(255, 255, 255, .1);
//  }
//}

@keyframes loading {
  from {
    width: 0%;
  }
  to {
    width: 100%;
  }
}


.msg-list .msg .audio, .msg-list .msg .audio2 {
  font-size: 9px !important;
  line-height: 14px !important;
  padding: 5px !important;
  box-sizing: border-box !important;
  height: auto !important;
}


.bg, .bg2 {
  background: url() right 0 no-repeat;
  width: 14px;
  height: 24px;
  background-size: 400%;
  position: relative;
  top: 5px;
}

.bg2 {
  transform: rotateY(200deg);
}

.voice-content {
  display: flex;
  align-items: center;
  cursor: pointer;
}

@keyframes voicePlay {
  0% {
    background-position: 0;
  }
  100% {
    background-position: 100%;
  }
}

.markdown :deep(.markdown-it) {

  img {
    width: 100%;
  }

  a {
    color: #0052d9;
    font-weight: bold;
  }

}


.loader {
  width: 15px;
  height: 15px;
  border: 2px solid #ffffff;
  border-bottom-color: #9cecb0;
  border-radius: 50%;
  box-sizing: border-box;
  animation: rotation-1 1s linear infinite;
}

@keyframes rotation-1 {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

</style>
